﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FluentAssertions;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Metadata.Reflection;
using LINQPad.OpenAccess.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Telerik.OpenAccess;
using Telerik.OpenAccess.Metadata;

namespace LINQPad.OpenAccess.Tests.Services
{
    [TestClass]
    public class DataContextSchemaProviderFixture
    {
        #region - helpers

        Mock<IReflectionService> CreateReflectionServiceMock( params EntitySetInfo[] entitySetInfos )
        {
            var mock = new Mock<IReflectionService>( );
            mock.Setup( m => m.IsContextType( It.IsAny<Type>( ) ) ).Returns( true );
            mock.Setup( m => m.GetEntitySetsFromContext( It.IsAny<Type>( ) ) ).Returns( ( ) => entitySetInfos );

            return mock;
        }
        Mock<Converters.AssociationMultiplicityToExplorerIconConverter> CreateIconConverterMock( Func<Multiplicity, Multiplicity, ExplorerIcon> convertCallback = null )
        {
            var converterMock = new Mock<Converters.AssociationMultiplicityToExplorerIconConverter>( );
            if ( convertCallback == null )
                converterMock.Setup( p => p.ToExplorerIcon( It.IsAny<Multiplicity>( ), It.IsAny<Multiplicity>( ), It.IsAny<ExplorerIcon>( ) ) ).Returns( ExplorerIcon.ManyToMany );
            else
                converterMock.Setup( p => p.ToExplorerIcon( It.IsAny<Multiplicity>( ), It.IsAny<Multiplicity>( ), It.IsAny<ExplorerIcon>( ) ) ).Returns( convertCallback );

            return converterMock;
        }
        private EntitySetInfo CreateEntitySetInfo<T, TSet>( Expression<Func<T, TSet>> entitySetSelector )
            where T : OpenAccessContextBase
            where TSet : IQueryable
        {
            return new EntitySetInfo
                        (
                            typeof( T )
                            , typeof( TSet ).GetGenericArguments( )[0]
                            , ( PropertyInfo ) ( ( MemberExpression ) entitySetSelector.Body ).Member
                        );
        }
        private EntitySetInfo CreateFluentSofiaCarsEntitySetInfo<T>( Expression<Func<TestModel.Fluent.SofiaCarsRental21Model, IQueryable<T>>> entitySetSelector )
        {
            return this.CreateEntitySetInfo<TestModel.Fluent.SofiaCarsRental21Model, IQueryable<T>>( entitySetSelector );
        }
        private IEnumerable<R> LeftOuterJoinMembers<R>( IList<ExplorerItem> explorerItem, MetaPersistentType metaType, Func<ExplorerItem, MetaMember, R> resultSelector )
        {
            var primitiveMembers = metaType.Members
                                           .OfType<MetaPrimitiveMember>( )
                                           .Select( m => new { Member = ( MetaMember ) m, Text = String.Format( "{0} ({1})", m.PropertyName, m.MemberType.Name ) } );
            var navMembers = metaType.Members
                                     .OfType<MetaNavigationMember>( )
                                     .Select( m => new { Member = ( MetaMember ) m, Text = m.PropertyName } );

            return explorerItem.GroupJoin
                            (
                                primitiveMembers.Union( navMembers )
                                , e => e.Text
                                , m => m.Text
                                , ( e, m ) => resultSelector( e, m.Any( ) ? m.First( ).Member : null )
                            );
        }

        #endregion

        /// <summary>
        /// Tests the basic schema generation.
        /// </summary>
        [TestMethod]
        public void TestBasicSchemaGeneration( )
        {
            var customersSet = this.CreateFluentSofiaCarsEntitySetInfo( c => c.Customers );
            var rentalOrdersSet = this.CreateFluentSofiaCarsEntitySetInfo( c => c.RentalOrders );
            var reflectionServiceMock = this.CreateReflectionServiceMock( customersSet, rentalOrdersSet );
            var iconConverterMock = this.CreateIconConverterMock( );

            var provider = new DataContextSchemaProvider( reflectionServiceMock.Object, iconConverterMock.Object );
            var container = new TestModel.Fluent.SofiaCarsRental21ModelFluentMetadataSource( ).GetModel( );

            var schema = provider.GetSchema( customersSet.ContextType, container );

            schema.Should( ).NotBeEmpty( );

            schema.Select( e => e.Text ).Should( ).ContainInOrder( new string[] { "Customers", "RentalOrders" } );

            var customersItem = schema.First( e => e.Text == "Customers" );
            var rentalOrdersItem = schema.First( e => e.Text == "RentalOrders" );
            var customersType = container.PersistentTypes.First( e => e.Name == "Customer" );

            customersItem.Tag.Should( ).Be( customersType );
            customersItem.Icon.Should( ).Be( ExplorerIcon.Table );
            customersItem.Kind.Should( ).Be( ExplorerItemKind.QueryableObject );
            customersItem.IsEnumerable.Should( ).BeTrue( );
            customersItem.Children.Should( ).NotBeNull( );

            var customerMembers = LeftOuterJoinMembers( customersItem.Children, customersType, ( e, m ) => new { Item = e, Member = m } ).ToArray( );
            // all mapped ?
            customerMembers.Should( ).NotContain( e => e.Member == null, "all members should have been mapped" );
            // proper tag set for all ?
            customerMembers.Should( ).OnlyContain( e => e.Item.Tag == e.Member );
            // all navigation members have hyperlink set ?
            var navPropertiesWithoutLink = customerMembers.Where( e => e.Member is MetaNavigationMember )
                                                          .Where( e => e.Item.HyperlinkTarget == null )
                                                          .Select( e => e.Member )
                                                          .ToArray( );
            Assert.AreEqual( 0, navPropertiesWithoutLink.Length, "all navigation members should have a hyperlink set !" );
            // check hyperlink: Customer.RentalOrders -> RentalOrder.Customer
            var customerRentalsOrderProperty = customerMembers.Where( p => p.Member.PropertyName == "RentalOrders" ).Single( );
            var rentalsCustomerProperty = rentalOrdersItem.Children.Where( c => c.Text == "Customer" ).Single( );

            Assert.AreNotEqual( rentalOrdersItem, customerRentalsOrderProperty.Item.HyperlinkTarget, "the link should point to the property not the entity itself !" );
            Assert.AreEqual( rentalsCustomerProperty, customerRentalsOrderProperty.Item.HyperlinkTarget );
            Assert.AreEqual( customerRentalsOrderProperty.Item, rentalsCustomerProperty.HyperlinkTarget );
        }
    }
}
